/** 
 * Copyright 2010 Erlacher Felix, Estgfaeller Wolfgang, Ferula Patrick
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package at.socialconference.inter.connection;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.junit.Test;

import at.socialconference.inter.connection.protocol.SocketProtocol;
import at.socialconference.inter.model.Conference;
import at.socialconference.inter.model.Login;
import at.socialconference.inter.model.Place;
import at.socialconference.inter.model.Talk;
import at.socialconference.inter.model.User;

public class SocketConnector {

	private final static Properties p;

	private static String ADDRESS;
	private static int PORT;

	static {
		p = new Properties();
		BufferedInputStream stream;
		try {
			stream = new BufferedInputStream(
					SocketConnector.class.getResourceAsStream("/at/socialconference/inter/connection/server.properties"));
			p.load(stream);
			stream.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		ADDRESS = p.getProperty("backendaddress", null);
		try {
			PORT = Integer.parseInt(p.getProperty("backendport"), 1999);
		} catch (NumberFormatException nfe) {
			PORT = 1981;
		}
		System.out.println("Backenaddress: " + ADDRESS);
		System.out.println("Backenport: " + PORT);

	}

	private ObjectInputStream in;
	private ObjectOutputStream out;
	private Socket socket;

	private String username;
	private String password;

	private int reconnectTries = SocketProtocol.RECONNECT_TRIES;

	public SocketConnector(String serverURL) {
		if (serverURL!=null){
			String[] s = serverURL.split(":");
			if(s!=null){
				ADDRESS=s[0];
				PORT=Integer.parseInt(s[1]);
			}
		}
		
	}

	public SocketConnector(){}
	
	private int reconnect() {
		if (reconnectTries <= 0)
			return -1;
		reconnectTries--;
		try {
			int status = connect(username, password);
			reconnectTries = SocketProtocol.RECONNECT_TRIES;
			return status;
		} catch (Exception e) {
			e.printStackTrace();
			return reconnect();
		}
	}

	public synchronized int checkLogin(String username, String password) {
		this.username = username;
		this.password = password;
		try {
			int result = connect(username, password);
			return result;
		} catch (Exception e) {
			e.printStackTrace();
			return reconnect();
		}
	} 

	public synchronized Conference getConferenceNow() throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_CONFERENCENOW);
			out.flush();
			Conference c = (Conference) in.readObject();
			return c;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getConferenceNow();
		}
	}

	@SuppressWarnings("unchecked")
	public synchronized List<Conference> getConferences() throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_GETCONFERENCES);
			out.flush();
			List<Conference> conferences = (List<Conference>) in.readObject();
			return conferences;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getConferences();
		}
	}
	
	@SuppressWarnings("unchecked")
	public synchronized List<Place> getPlaces() throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_GETPLACES);
			out.flush();
			List<Place> places = (List<Place>) in.readObject();
			return places;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getPlaces();
		}
	}
	
	@SuppressWarnings("unchecked")
	public synchronized List<Talk> getTalks() throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_GETTALKS);
			out.flush();
			List<Talk> talks = (List<Talk>) in.readObject();
			return talks;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getTalks();
		}
	}


	public synchronized int friendRequest(Integer id) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_FRIENDREQUEST);
			out.writeInt(id);
			out.flush();
			return in.readInt();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return friendRequest(id);
		}
	}
	
	public synchronized int acceptFriendRequest(Integer id) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_ACCEPT_FRIENDREQUEST);
			out.writeInt(id);
			out.flush();
			return in.readInt();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return friendRequest(id);
		}
	}
	
	public synchronized int rejectFriendRequest(Integer id) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_REJECT_FRIENDREQUEST);
			out.writeInt(id);
			out.flush();
			return in.readInt();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return friendRequest(id);
		}
	}

	/**
	 * Adds a Twitter Account Token to the current User
	 * 
	 * @param tw_at
	 *            Twitter Account Token
	 * @param tw_at_secret
	 *            Twitter Account Token Secret
	 * @return StatusCode
	 * @throws Exception
	 */
	public synchronized int addTwitterTokens(String tw_at, String tw_at_secret) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_ADDTWITTERTOKENS);
			out.writeUTF(tw_at);
			out.writeUTF(tw_at_secret);
			out.flush();
			return in.readInt();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return addTwitterTokens(tw_at, tw_at_secret);
		}
	}

	/**
	 * Fetch Twitter Account Data from the Server
	 * 
	 * @return String[] pos. 0: Twitter Access Token pos. 1: Twitter Account
	 *         Token Secret
	 * @throws Exception
	 */
	private synchronized String[] getTwitterTokens() throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_GETTWITTERTOKENS);
			out.flush();

			String tw_at = in.readUTF();
			String tw_at_secret = in.readUTF();
			return new String[] { tw_at, tw_at_secret };
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getTwitterTokens();
		}
	}

	public synchronized ArrayList<User> getFriendRequests() throws Exception {
		try {
			ArrayList<User> requests = new ArrayList<User>();
			out.writeUTF(SocketProtocol.CMD_GETFRIENDREQUESTS);
			out.flush();
			int count = in.readInt();
			for (int i = 0; i < count; i++) {
				User c = (User) in.readObject();
				requests.add(c);
			}
			return requests;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getFriendRequests();
		}
	}

	/**
	 * Returns own User
	 * @return own User
	 * @throws Exception
	 */
	public synchronized User getUser() throws Exception {
		return getUser(-1);
	}


	/**
	 * Returns user with specific id
	 * @param id user-id
	 * @return user
	 * @throws Exception
	 */
	public synchronized User getUser(int id) throws Exception{
		try {
			out.writeUTF(SocketProtocol.CMD_GETUSER);
			out.writeInt(id);
			out.flush();
			User c = (User) in.readObject();
			return c;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return getUser();
		}

	}

	public synchronized ArrayList<User> searchUser(String firstName, String lastName, int countryCode) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_SEARCHUSER);
			out.writeUTF(firstName != null ? firstName : "");
			out.writeUTF(lastName != null ? lastName : "");
			out.writeInt(countryCode);
			out.flush();
			int count = in.readInt();

			ArrayList<User> result = new ArrayList<User>();
			for (int i = 0; i < count; i++) {
				User u = (User) in.readObject();
				result.add(u);
			}
			return result;
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return searchUser(firstName, lastName, countryCode);
		}
	}

	public synchronized int register(User user, Login login) throws Exception {
		int i = checkLogin("registrationAccount", "S0c1@lC0nF3r3nC3");
		if (i == SocketProtocol.OK) {
			out.writeUTF(SocketProtocol.CMD_REGISTER);
			out.writeObject(login);
			out.writeObject(user);
			out.flush();
			int status = in.readInt();
			close();
			return status;
		} else {
			throw new Exception(this.getClass().getName() + " ERROR: NO CONNECTION TO SERVER - STATUSCODE: " + i);
		}
	}

	public synchronized int checkFriendStatus(Integer id) throws Exception {
		try {
			System.out.println(">>>>>>>>>>>>check fs!!!!!!");
			out.writeUTF(SocketProtocol.CMD_CHECKFRIENDSTATUS);
			out.writeInt(id);
			out.flush();
			return in.readInt();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return checkFriendStatus(id);
		}
	}


	public boolean isEnrolled(Integer id) throws Exception{
		try {
			out.writeUTF(SocketProtocol.CMD_PARTICIPATESCHECK);
			out.writeInt(id);
			out.flush();
			return in.readBoolean();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return isEnrolled(id);
		}
	}
	
	/**
	 * Enrolls Conference
	 * @param id Conference ID
	 * @return   1 - OK
	 * 			-1 - FAILURE
	 *         601 - ALREADY ENROLLED
	 * @throws Exception
	 */
	public int enroll(Integer id) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_ENROLLCONFERENCE);
			out.writeInt(id);
			out.flush();
			return in.readInt();
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return enroll(id);
		}
	}


	public synchronized int updateObject(Object object) throws IOException {
		out.writeUTF(SocketProtocol.CMD_UPDATE_OBJECT);
		out.writeObject(object);
		out.flush();
		return in.readInt();
	}

	public synchronized int insertObject(Object object) throws IOException {
		out.writeUTF(SocketProtocol.CMD_INSERT_OBJECT);
		out.writeObject(object);
		out.flush();
		return in.readInt();
	}

	public synchronized int deleteObject(Object object) throws IOException {
		out.writeUTF(SocketProtocol.CMD_DELETE_OBJECT);
		out.writeObject(object);
		out.flush();
		return in.readInt();
	}

	public synchronized Object handleQRCode(String contents) throws Exception {
		try {
			out.writeUTF(SocketProtocol.CMD_QRCODE);
			out.flush();
			out.writeUTF(contents);
			out.flush();

			int type = in.readInt();
			if (type == SocketProtocol.RESP_QR_PEOPLE) {
				User u = (User) in.readObject();
				return u;
			} else if (type == SocketProtocol.RESP_QR_TALK) {
				Talk t = (Talk) in.readObject();
				return t;
			} else if (type == SocketProtocol.RESP_QR_CONFERENCE) {
				Conference con = (Conference) in.readObject();
				return con;
			} else {
				return null;
			}
		} catch (Exception e) {
			if (reconnect() == -1)
				throw e;
			else
				return handleQRCode(contents);
		}

	}

	public synchronized int connect(String username, String password) throws Exception {
		socket = new Socket(ADDRESS, PORT);
		in = new ObjectInputStream(socket.getInputStream());
		out = new ObjectOutputStream(socket.getOutputStream());
		out.writeUTF(username);
		out.writeUTF(password);
		out.flush();
		return in.readInt();
	}

	public synchronized void close() throws Exception {
		out.writeUTF(SocketProtocol.CMD_GOODBYE);
		out.flush();
		in.close();
		out.close();
		socket.close();
	}

	// TODO Test method... remove
	public synchronized void send(String msg) {
		try {
			out.writeUTF(msg);
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testLogin() throws IOException {
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
		System.out.println(checkLogin("test", "123"));
		// System.out.println(checkLogin("testihallo", "wrongpw"));
		while (true) {
			String cmd = console.readLine();
			send(cmd);
		}
	}

	@Test
	public void testRegister() throws IOException {
		User u = new User(null, "patzo", "aaa", "m", "666", "dr.dr.", "google.de", "www.dblp.com", "Austria", "Austria", "sdf", "324",
				"sf@dsf.de", null, null, null);
		Login l = new Login();
		l.setUser("patzooo");
		l.setPwd("xxx");
		try {
			System.out.println(register(u, l));
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testCheckFriendShip() throws IOException {
		try {
			connect("User", "password");
			checkFriendStatus(4);

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testSearch() throws IOException {
		ArrayList<User> users;
		try {
			connect("User", "password");
			users = searchUser("Pat", null, -1);

			System.out.println(users.size());
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testAccessTokens() throws IOException {

		try {
			connect("User", "password");

			addTwitterTokens("test", "thisismysecret");
			String[] tokens = getTwitterTokens();
			System.out.println(tokens[0] + "  " + tokens[1]);

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testFriendRequests() throws IOException {

		try {
			connect("User", "password");

			ArrayList<User> reqs = getFriendRequests();
			if (reqs != null)
				System.out.println(reqs.size());

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testGetUsers() throws IOException {

		try {
			connect("Patrick", "patrick");

			User u = getUser(19);

			System.out.println(u.getFirstname());

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testGetConference() throws IOException {

		try {
			connect("Patrick", "patrick");

			Conference c = getConferenceNow();

			System.out.println(c.getTalks().size());

		} catch (Exception e) {
			e.printStackTrace();
		}

	}
	
	@Test
	public void testParticipatesConference() throws IOException {

		try {
			connect("Patrick", "patrick");
			
			System.out.println(isEnrolled(211959055));
			System.out.println(isEnrolled(211959052));
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testEnroll() throws IOException {

		try {
			connect("Patrick", "patrick");
			System.out.println(enroll(211959237));
			System.out.println(enroll(211959055));
			System.out.println(enroll(211959052));
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Test
	public void testGetPlaces() throws IOException {

		try {
			connect("Patrick", "patrick");
			System.out.println(getPlaces().size());
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}
